/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2013 Jean-Sebastien Lerat (Jean-Sebastien.Lerat@ulb.ac.be)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 *
 * @author Jean-Sebastien Lerat (Jean-Sebastien.Lerat@ulb.ac.be)
 * @version 1.00, 24/06/2013
 * Native implementation of the measure library for CoNet (http://psbweb05.psb.ugent.be/conet/)
 */

#include "be_ac_ulb_mlg_utils_Measurer.h"
#include <pthread.h>
#include "src/measures.h"
#include "src/renormalizer.h"

#define is_missing(x) isnan(x)


int fill_missing(jboolean *,jdouble *,jdouble *, jsize);
jboolean handle_missing(JNIEnv *,jobject);
jboolean using_shuffling(JNIEnv *,jobject);
jboolean pre_shuffle(JNIEnv * ,jobject);
jint getNumberOfThreads(JNIEnv *,jobject);
jint getMaximumMissingValues(JNIEnv *,jobject);
int check_missing(jdouble* data[],jboolean* measurable[],jsize N,jsize M,jint maxMissingValues);
jdouble **  duplicate_sum(jdouble ** sum,const int groupCount,const jsize M);
//void shuffle_noreplacement(jdouble * input,const jsize N);

void shuffle_with_replacement(jdouble * input,jdouble * shuffled, const jsize M,const int missing);
void shuffle_without_replacement(jdouble * input,jdouble * shuffled, const jsize M,const int missing);

////// BODY OF METHODS
int check_missing(jdouble* data[],jboolean* measurable[],jsize N,jsize M,jint maxMissingValues){
	int count = 0;

	int current,other,pos;

	for(current=0; current<N-1; current++){
		for(other=current+1; other<N; other++){
			int missing = 0;//for include the maxMissingValues amount
			for(pos=0;pos<M && missing<=maxMissingValues;pos++){
				if(is_missing(data[current][pos]) || is_missing(data[other][pos]))
					missing++;
			}
			if(missing>maxMissingValues){
				count++;
				//Both (diag matrix) are not useful but prevent bug for extension of this application
				measurable[current][other]=measurable[other][current]=JNI_FALSE;
			}
		}
	}

	return count;
}

jint getMaximumMissingValues(JNIEnv * env,jobject measurer){
	jclass klass		= (*env)->GetObjectClass(env,measurer);
	jmethodID method = (*env)->GetMethodID(env, klass,"getMaximumMissingValues", "()I");
	return (*env)->CallIntMethod(env, measurer, method);
}

jint getNumberOfThreads(JNIEnv * env,jobject measurer){
	jclass klass		= (*env)->GetObjectClass(env,measurer);
	jmethodID method = (*env)->GetMethodID(env, klass,"getNumberOfThreads", "()I");
	return (*env)->CallIntMethod(env, measurer, method);
}
jboolean handle_missing(JNIEnv * env,jobject measurer){
	jclass klass		= (*env)->GetObjectClass(env,measurer);
	jmethodID method	= (*env)->GetMethodID(env,klass, "isHandelingMissingValues", "()Z");
	return (*env)->CallBooleanMethod(env,measurer,method);
}
jboolean using_shuffling(JNIEnv * env,jobject measurer){
	jclass klass		= (*env)->GetObjectClass(env,measurer);
	jmethodID method	= (*env)->GetMethodID(env,klass, "isUsingShuffling", "()Z");
	return (*env)->CallBooleanMethod(env,measurer,method);
}
jboolean pre_shuffle(JNIEnv * env,jobject measurer){
	jclass klass		= (*env)->GetObjectClass(env,measurer);
	jmethodID method	= (*env)->GetMethodID(env,klass, "preShuffle", "()Z");
	return (*env)->CallBooleanMethod(env,measurer,method);
}
jdouble **  duplicate_sum(jdouble ** sum,const int groupCount,const jsize M){
	if(sum == NULL) return NULL;
	//TODO make weak global ref
	jdouble ** copy = (jdouble**) malloc(sizeof(jdouble*) * groupCount);
	int i,j;
	for(i=0;i<groupCount;i++){
		copy[i] = (jdouble*) malloc(sizeof(jdouble) * M);
		for(j=0;j<M;j++){
			copy[i][j] = sum[i][j];
		}
	}
	return copy;
}

void shuffle_with_replacement(jdouble * input,jdouble * shuffled, const jsize M,const int missing){
	if(missing){
		int i = M, n = M, j= 0;
		while(i){
			if(is_missing(input[i--])){
				--n;
			}
		}
		int notNaN[n];
		i = M;

		while(i){
			if(!is_missing(input[i])){
				notNaN[j++] = i;
			}
			--i;
		}

		for(i=0;i<n;i++){
			*shuffled = notNaN[RAND_INT(n)];
			shuffled++;
		}

		for(;i<M;i++){
			*shuffled = NAN;
		}

	}else{
		jsize ptr = M;
		while(ptr){
			*shuffled = input[RAND_INT(M)];
			shuffled++;
			ptr--;
		}
	}
}

void shuffle_without_replacement(jdouble * input,jdouble * shuffled, const jsize M,const int missing){
	int i,swap;
	int n = 0;
	jdouble tmp;

	if(missing){
		for(i=0;i<M;i++){
			if(! is_missing(input[i])){
				shuffled[n++] = input[i];
			}
		}
		for(i=n;i<M;i++)
			shuffled[i] = NAN;
	}else{
		n = M;
		for(i=0;i<M;i++)
			shuffled[i] = input[i];

	}
	for(i=0;i<n;i++){
		swap					= RAND_INT(n);
		tmp					= shuffled[swap];
		shuffled[swap]	= shuffled[i];
		shuffled[i]			= tmp;
	}
}
/*
void shuffle_noreplacement(jdouble * input,const jsize N){
	jsize ptr = N;
	jdouble backup;
	int swap;
	while(--ptr){
		swap		= RAND_INT(N);
		backup		= input[ptr];
		input[ptr]	= input[swap];
		input[swap]	= backup;
	}
}*/

int fill_missing(jboolean * computable,jdouble * a,jdouble * b, jsize len){
	if(computable == NULL) return 0;
	a				+= len-1;
	b				+= len-1;
	computable		+= len-1;

	int count = 0;

	while(len){
		*computable = (jboolean)(!(is_missing(*a)||is_missing(*b)));
		if(! *computable) count++;
		len--;
		a--;
		b--;
		computable--;
	}

	return count;
}

typedef struct ThreadData{
	JavaVM *jvm;
	int start,end;
	jsize M,N,MEASURE_COUNT;
	jboolean ** allowed;
	jboolean handle_missing;
	jdouble ** data;
	MeasureFunction * functions;
	jobjectArray * results;
	jboolean isUsingShuffle;
	jdouble max_missing;
	DefaultRenormalizerConfig renorm;
	#if THREAD_SAFE == 1
		pthread_mutex_t * result_mutex;
	#endif
} ThreadData;

void * measure_thread(void * data_tread){
	// data to be fetched from data_thread
	int current,last;
	JNIEnv * env;
	jsize M,N,MEASURE_COUNT;
	MeasureFunction * functions;
	jdouble ** data;
	jdouble * first;
	jdouble * second;
	jboolean * missing_ptr;
	jboolean ** allowed;
	jobjectArray * results;
	jboolean isUsingShuffle,pairShuffle;
	jdouble max_missing;
	DefaultRenormalizerConfig * config;

	//additional variables
	jboolean missing_free[M];
	int measure,other,ptr;
	//fetch data
	{
		ThreadData params = *((ThreadData*)data_tread);
		//we can attach the first thread (it's already assigned so no effect)
		(*(params.jvm))->AttachCurrentThread(params.jvm,(void **)&env,NULL);
		current			= params.start;
		last			= params.end;
		M				= params.M;
		N				= params.N;
		MEASURE_COUNT	= params.MEASURE_COUNT;
		functions		= params.functions;
		data			= params.data;
		missing_ptr		= params.handle_missing ? missing_free : NULL;
		allowed			= params.allowed;
		results			= params.results;
		isUsingShuffle	= params.isUsingShuffle;
		max_missing		= params.max_missing;
		config			= &(params.renorm);
	}
	int count	= N - current - 1;
	if(config->pairShuffle || isUsingShuffle){//TODO weak glob ref?
		first = (jdouble*) malloc(sizeof(jdouble)*M);
		second = (jdouble*) malloc(sizeof(jdouble)*M);
	}
	if(allowed != NULL){
		for(; current<last; current++){
			//create temp vector measures
			jdoubleArray currentXother[MEASURE_COUNT];//Can be better, use a raw array of 1024 double and put it into the array when it is filled
			//jboolean invert = JNI_FALSE;
			for(measure=0;measure<MEASURE_COUNT;measure++){
				currentXother[measure]	= (*env)->NewDoubleArray(env, count);
				currentXother[measure]	= (jdoubleArray)(*env)->NewWeakGlobalRef(env,currentXother[measure]);
			}
			if(config->pairShuffle){//copy values of the first vectors
				updateRenormalization(config,data[current], current, 1);
			}else if(!isUsingShuffle){
				first = data[current];
			}
			//do the computation
			for(other=current+1,ptr=0; other<N; other++,ptr++){
				if(allowed[current][other]){

					if(config->pairShuffle || isUsingShuffle){//pair shuffle
						if(config->pairShuffle){
							updateRenormalization(config,data[other], other, 1);
							/*shuffle_with_replacement*/shuffle_without_replacement(data[current],first,M,1);
							/*shuffle_with_replacement*/shuffle_without_replacement(data[other],second,M,1);
							simulateRenormalization(config,first,second,current,other);
							updateRenormalization(config,data[other], other, 0);
						}else{
							shuffle_without_replacement(data[current],first,M,1);
							shuffle_without_replacement(data[other],second,M,1);
						}
					}else{
						second = data[other];
					}

					if(fill_missing(missing_ptr,first,second,M) > max_missing){
						//Set forbidden pair to NaN
						const jdouble forbidden = NAN;
						for(measure=0;measure<MEASURE_COUNT;measure++)
							(*env)->SetDoubleArrayRegion(env, currentXother[measure], ptr,1, &forbidden);
					}else{
						//for each measures, compute measure(data[current],data[other],size)
						for(measure=0;measure<MEASURE_COUNT;measure++){
							const jdouble result = functions[measure].function(
									functions[measure].config,
									first,
									second,
									missing_ptr,
									M
							);
							(*env)->SetDoubleArrayRegion(env, currentXother[measure], ptr,1, &result);
						}
					}
				}else{
					//Set forbidden pair to NaN
					const jdouble forbidden = NAN;
					for(measure=0;measure<MEASURE_COUNT;measure++)
						(*env)->SetDoubleArrayRegion(env, currentXother[measure], ptr,1, &forbidden);
				}
			}
			//assign current result into the whole result matrix
			#if THREAD_SAFE == 1
				pthread_mutex_lock(((ThreadData*)data_tread)->result_mutex);
			#endif
			for(measure=0;measure<MEASURE_COUNT;measure++){
				(*env)->SetObjectArrayElement(env,results[measure],current,currentXother[measure]);
			}
			#if THREAD_SAFE == 1
				pthread_mutex_unlock(((ThreadData*)data_tread)->result_mutex);
			#endif
			//next
			count--;
			if(config->pairShuffle) updateRenormalization(config,data[current], current, 0);
		}
	} else {
		//SAME WITHOUT ALLOWED CHECKING
		for(; current<last; current++){
			jdoubleArray currentXother[MEASURE_COUNT];
			//jboolean invert = JNI_FALSE;
			for(measure=0;measure<MEASURE_COUNT;measure++){
				currentXother[measure]	= (*env)->NewDoubleArray(env, count);
				currentXother[measure]	= (jdoubleArray)(*env)->NewWeakGlobalRef(env,currentXother[measure]);
			}
			if(config->pairShuffle){//copy values of the first vectors
				updateRenormalization(config,data[current], current, 1);
			}else if(!isUsingShuffle){
				first = data[current];
			}
			for(other=current+1,ptr=0; other<N; other++,ptr++){
				if(config->pairShuffle || isUsingShuffle){//pair shuffle
					if(config->pairShuffle){
						updateRenormalization(config,data[other], other, 1);
						/*shuffle_with_replacement*/shuffle_without_replacement(data[current],first,M,0);
						/*shuffle_with_replacement*/shuffle_without_replacement(data[other],second,M,0);
						simulateRenormalization(config,first,second,current,other);
						updateRenormalization(config,data[other], other, 0);
					}else{
						shuffle_without_replacement(data[current],first,M,0);
						shuffle_without_replacement(data[other],second,M,0);
					}
				}else{
					second = data[other];
				}

				fill_missing(missing_ptr,first,second,M);
				//for each measures, compute measure(data[current],data[other],size)
				for(measure=0;measure<MEASURE_COUNT;measure++){
					const jdouble result = functions[measure].function(
							functions[measure].config,
							first,
							second,
							missing_ptr,
							M
					);
					(*env)->SetDoubleArrayRegion(env, currentXother[measure], ptr,1, &result);
				}
			}
			//assign current result into the whole result matrix
			#if THREAD_SAFE == 1
				pthread_mutex_lock(((ThreadData*)data_tread)->result_mutex);
			#endif
			for(measure=0;measure<MEASURE_COUNT;measure++){
				(*env)->SetObjectArrayElement(env,results[measure],current,currentXother[measure]);
			}
			#if THREAD_SAFE == 1
				pthread_mutex_unlock(((ThreadData*)data_tread)->result_mutex);
			#endif
			//next
			count--;
			if(config->pairShuffle) updateRenormalization(config,data[current], current, 0);
		}
	}

	#if FORCE_FREE == 1
		if(config->pairShuffle || isUsingShuffle){
			free(first);
			free(second);
		}
	#endif

	//DetachCurrentThread but not for the 1st before exiting
	(*(((ThreadData*)data_tread)->jvm))->DetachCurrentThread(((ThreadData*)data_tread)->jvm);
	return NULL;
}

JNIEXPORT void JNICALL Java_be_ac_ulb_mlg_utils_Measurer_initMeasurer(JNIEnv * env, jclass caller){
	srand(time(NULL));
}

JNIEXPORT jobject JNICALL Java_be_ac_ulb_mlg_utils_Measurer_measureNative(JNIEnv * env, jobject caller, jobjectArray input, jobjectArray measures,jobjectArray measurable, jint M){
	//initialize
	const jsize N					= (*env)->GetArrayLength(env, input);
	const jsize MEASURE_COUNT		= (*env)->GetArrayLength(env, measures);
	const jclass doubleArrayClass	= (*env)->FindClass(env, "[D");
	jsize current,other,measure;
	//fetch data (and measurable) and put them into 'data' (allowed)
	jdouble ** data = (jdouble **)malloc(sizeof(jdouble *)*N);

	jboolean ** allowed = (jboolean **)malloc(sizeof(jboolean *)*N);
	if(measurable != NULL){
		for(current=0; current<N; current++){
			data[current]		= (*env)->GetDirectBufferAddress(env,(*env)->GetObjectArrayElement(env,input,current));
			allowed[current]	= (*env)->GetDirectBufferAddress(env,(*env)->GetObjectArrayElement(env,measurable,current));
		}
	}else{
		for(current=0; current<N; current++)
			data[current]		= (*env)->GetDirectBufferAddress(env,(*env)->GetObjectArrayElement(env,input,current));
		allowed[0] = NULL;//ensure that the first value is defined (know we don't need to free it)
	}
	DefaultRenormalizerConfig * renormConf = defaultRenormalizer(
				env,
				(*env)->GetObjectField(env,caller,(*env)->GetFieldID(env,(*env)->GetObjectClass(env,caller),"renormalizer",ABSTRACT_RENORMALIZER)),
				data,
				N,
				(jsize)M
	);
	//create/init results arrays, one per measure and configure measures
	jobjectArray results[MEASURE_COUNT];
	MeasureFunction functions[MEASURE_COUNT];
	for(measure=0;measure<MEASURE_COUNT;measure++){
		functions[measure]		= defineMeasure(env,(*env)->GetObjectArrayElement(env,measures,measure));
		results[measure]		= (*env)->NewObjectArray(env,N-1,doubleArrayClass,NULL);
	}
	// compute measures as an upper triangular matrix
	//jsize count = N-1;
	jsize ptr;
	//pre-shuffle
	if(pre_shuffle(env,caller)){
		const int missing = handle_missing(env,caller);
		int j;
		jdouble copy[M];
		for(ptr=0;ptr<N;ptr++){
			for(j=0;j<M;j++){
				copy[j] = data[ptr][j];
			}
			shuffle_without_replacement(copy,data[ptr],M,missing);
		}
	}
	if(handle_missing(env,caller)){
		if(measurable != NULL) check_missing(data,allowed,N,M,getMaximumMissingValues(env,caller));
		else{
			for(current=0;current<N;current++){
				allowed[current] = (jboolean*)malloc(sizeof(jboolean)*N);
				for(ptr=0;ptr<N;ptr++)
					allowed[current][ptr]=JNI_TRUE;
			}
				if(!check_missing(data,allowed,N,M,getMaximumMissingValues(env,caller))){
					#if FORCE_FREE == 1
					for(current=0;current<N;current++)
						free(allowed[current]);
					#endif
					allowed[0] = NULL;//ensure that the first value is defined (know we don't need to free it)
				}
		}
	}
	const int threads_count = getNumberOfThreads(env,caller);
	pthread_t threads[threads_count];
	ThreadData threads_data[threads_count];
	JavaVM *jvm;
	(*env)->GetJavaVM(env, &jvm);

	#if THREAD_SAFE == 1
		pthread_mutex_t result_mutex = PTHREAD_MUTEX_INITIALIZER;
	#endif
	{
		threads_data[0].N				= N;
		threads_data[0].handle_missing	= handle_missing(env,caller);
		threads_data[0].M				= M;
		threads_data[0].MEASURE_COUNT	= MEASURE_COUNT;
		threads_data[0].jvm				= jvm;
		threads_data[0].functions		= functions;
		threads_data[0].results			= results;
		threads_data[0].data			= data;
		threads_data[0].allowed			= allowed[0] != NULL ? allowed : NULL;
		threads_data[0].start 			= 0;
		threads_data[0].isUsingShuffle	= using_shuffling(env,caller);
		threads_data[0].max_missing		= getMaximumMissingValues(env,caller);
		if(renormConf != NULL){
			threads_data[0].renorm.groupCount			= renormConf->groupCount;
			threads_data[0].renorm.M					= renormConf->M;
			threads_data[0].renorm.pairShuffle			= renormConf->pairShuffle;
			threads_data[0].renorm.groups				= renormConf->groups;
			threads_data[0].renorm.specificSize			= renormConf->specificSize;
			threads_data[0].renorm.specificNormalizer	= renormConf->specificNormalizer;
			threads_data[0].renorm.sum					= renormConf->sum;
			threads_data[0].renorm.features				= renormConf->features;
		}else{
			threads_data[0].renorm.groupCount			= 1;
			threads_data[0].renorm.M					= M;
			threads_data[0].renorm.pairShuffle			= JNI_FALSE;
			threads_data[0].renorm.groups				= NULL;
			threads_data[0].renorm.specificSize			= NULL;
			threads_data[0].renorm.specificNormalizer	= NULL;
			threads_data[0].renorm.sum					= NULL;
			threads_data[0].renorm.features				= NULL;
		}
		#if THREAD_SAFE == 1
			threads_data[0].result_mutex = &result_mutex;
		#endif
		const double part = ((N*(N-1))>>1)/((double)threads_count);
		const double nscale = N+0.5;
		for(current=1;current<threads_count;current++){
			threads_data[current].M				= M;
			threads_data[current].handle_missing=threads_data[0].handle_missing;
			threads_data[current].N				= N;
			threads_data[current].jvm			= jvm;
			threads_data[current].MEASURE_COUNT	= MEASURE_COUNT;
			threads_data[current].functions		= functions;
			threads_data[current].data			= data;
			threads_data[current].allowed		= threads_data[0].allowed;
			threads_data[current].results		= results;
			threads_data[current].isUsingShuffle= threads_data[0].isUsingShuffle;
			threads_data[current].max_missing	= threads_data[0].max_missing;
			threads_data[current].renorm.groupCount				= threads_data[0].renorm.groupCount;
			threads_data[current].renorm.M						= threads_data[0].renorm.M;
			threads_data[current].renorm.pairShuffle			= threads_data[0].renorm.pairShuffle;
			threads_data[current].renorm.groups					= threads_data[0].renorm.groups;
			threads_data[current].renorm.specificSize			= threads_data[0].renorm.specificSize;
			threads_data[current].renorm.specificNormalizer		= threads_data[0].renorm.specificNormalizer;
			threads_data[current].renorm.features				= threads_data[0].renorm.features;
			threads_data[current].renorm.sum					= duplicate_sum(threads_data[0].renorm.sum,threads_data[0].renorm.groupCount,M);
			#if THREAD_SAFE == 1
				threads_data[current].result_mutex = &result_mutex;
			#endif
			/*
			 The total amount of elements is = sum_{i=1}^{n}(n-i) = n*(n-1)/2
			 Thus, for a particular line Line, we have compute sum_{i=1}^{Line}(n-i)
			 <=> sum_{i=1}^{Line}(n) - sum_{i=1}^{Line}(i)
			 <=> Line*n - Line*(Line+1)/2
			 <=> Line * [n - (Line+1)/2]
			 For the simplest divide, we want to find the lines such that the previous calculation + a new part of the total (n*(n-1)/2)/THREADS
			 will compute at least a complete part of the total (n*(n-1)/2)/THREADS
			 Example with 2 threads, n=100:
				 Total			= 100*(100-1)/2	= 4950
				 A total part	= Total / 2		= 2475
				 The best simplest end for thread_1 is computed from: 0 (previous end range) + 4950
				 So, we need to search Line such that at least 4950 data will be computed
				 => Line * [n - (Line+1)/2] = 4950
				 => n*Line - 0.5*Line^2 - 0.5*Line = 4950
				 => - 0.5*Line^2 + (n-0.5)*Line - 4950 = 0
				 => rho = (n-0.5)^2 - 4*[(- 0.5)*(-4950)]
				 =>     = n^2 + 0.25 - n - 4*2475
				 =>     = n^2 - n - 9899.75
				 => Line = n-0.5 + sqrt(n^2 - n - 9899.75)     OU   n-0.5 - sqrt(n^2 - n - 9899.75)
			 */
			threads_data[current].start 	= (int)round(nscale - sqrt(nscale*nscale - 2.0*part*(current+1.0)));
			threads_data[current-1].end		= threads_data[current].start;
		}

		threads_data[threads_count-1].end	= N-1;
	}
	for(current=0;current<threads_count;current++){
		pthread_create(&(threads[current]),NULL,measure_thread,&(threads_data[current]));
	}
	for(current=0;current<threads_count;current++){
		pthread_join(threads[current],NULL);
	}
	#if FORCE_FREE == 1
		//free allow mem if we created for check_missing
		if(allowed[0]!=NULL){
			for(current=0;current<N;current++){
				free(allowed[current]);
			}
		}
		free(allowed);

	for(current=1;current<threads_count;current++)
		free(threads_data[current].renorm.sum);
	 free_renormalizer_config(env,renormConf);
	#endif

	//Format result
	jclass mapClass = (*env)->FindClass(env, "java/util/HashMap");
	jmethodID init = (*env)->GetMethodID(env, mapClass, "<init>", "(I)V");//default constructor
	jobject hashMap = (*env)->NewObject(env, mapClass, init, MEASURE_COUNT);
	jmethodID put = (*env)->GetMethodID(env, mapClass, "put","(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
	for(measure=0; measure<MEASURE_COUNT; measure++){
		jobject e = (*env)->GetObjectArrayElement(env,measures,measure);
		(*env)->CallObjectMethod(env, hashMap, put, (*env)->GetObjectArrayElement(env,measures,measure), results[measure]);
		#if FORCE_FREE == 1
			free(functions[measure].config);
		#endif
	}
	return hashMap;
}
